package uplb.ics.cs190.e2d.display;

import java.awt.Canvas;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.awt.image.VolatileImage;

import uplb.ics.cs190.e2d.graphics.Color;
import uplb.ics.cs190.e2d.graphics.Image;
import uplb.ics.cs190.e2d.math.Dimension;

/**
 * The <code>WindowedMode</code> class represents a basic windowed display
 * in the J2SE environment for PCs.
 * <p>
 * This class is derived from the graphics engine of GTGE. There are some
 * minor changes in the source code as to suit the portability framework of
 * the Etherion2D. This is a system dependency class which means that it
 * is designed to work only for J2SE (Personal Computer).
 * 
 * @author GTGE
 * @author JPACarabuena
 */
public class WindowedMode implements Display {

	/**
	 * The graphics device that constructs this graphics engine. 
	 */
	public static final GraphicsDevice DEVICE = GraphicsEnvironment.getLocalGraphicsEnvironment()
			.getDefaultScreenDevice();
	
	/**
	 * The graphics configuration that constructs this graphics engine.
	 */
	public static final GraphicsConfiguration CONFIG = DEVICE.getDefaultConfiguration();
	
	private Frame frame;
	private Canvas canvas;
	
	private Dimension size;
	
	private VolatileImage offscreen;
	
	private BufferStrategy strategy;
	
	/**
	 * The current graphics context for this <code>WindowedMode</code> display.
	 */
	private J2SEGraphics currentGraphics;

	/**
	 * Creates a new instance of a <code>WindowedMode</code> display with the
	 * specified size and boolean flag that determines whether to use a <code>
	 * BufferStrategy</code> or <code>VolatileImage</code>.
	 * 
	 * @param d	the size of the window.
	 * @param bufferstrategy	if a bufferstrategy shall be used.
	 * @see BufferStrategy
	 * @see VolatileImage
	 */
	public WindowedMode(Dimension d, String title, boolean bufferstrategy) {
		this.size = d;
		this.currentGraphics = new J2SEGraphics();
		
		// sets the frame
		this.frame = new Frame(title, WindowedMode.CONFIG);
		
		/* INSERT HERE ICON */
		
		this.frame.setResizable(false);	// non-resizable frame
		this.frame.setIgnoreRepaint(true);	// turn-off all paint events
											// since we are doing active rendering
			
		// active component where the game is drawn
		this.canvas = new Canvas(WindowedMode.CONFIG);
		this.canvas.setIgnoreRepaint(true);
		this.canvas.setSize(this.size.width, this.size.height);
		
		// frame title bar and border (frame insets) makes
		// game screen smaller than requested size
		// we must enlarge the frame by it's insets size
		this.frame.setVisible(true);
		Insets inset = this.frame.getInsets();
		this.frame.setVisible(false);
		this.frame.setSize(this.size.width + inset.left + inset.right
				, this.size.height + inset.top + inset.bottom);
		this.frame.add(this.canvas);
		this.frame.pack();
		this.frame.setLayout(null);
		this.frame.setLocationRelativeTo(null);	// centering the frame
		if(this.frame.getX() < 0) {
			this.frame.setLocation(0, this.frame.getY());
		}
		if(this.frame.getY() < 0) {
			this.frame.setLocation(this.frame.getX(), 0);
		}
		this.frame.setVisible(true);
		
		// create backbuffer
		if(bufferstrategy) {
			bufferstrategy = this.createBufferStrategy();
		}
		
		if(!bufferstrategy) {
			this.createBackBuffer();
		}
		
		this.canvas.requestFocus();
	}

	/**
	 * Create an off screen buffer using a volatile image.
	 */
	private void createBackBuffer() {
		if(this.offscreen != null) {
			this.offscreen.flush();
			this.offscreen = null;
		}
		this.offscreen = WindowedMode.CONFIG.createCompatibleVolatileImage(
				this.size.width, this.size.height);
	}

	/**
	 * Create an off screen buffer using buffer strategy. Returns
	 * whether the buffer strategy is successfully created or not.
	 * 
	 * @return	if the buffer strategy is created or not.
	 */
	private boolean createBufferStrategy() {
		boolean bufferCreated;
		int num = 0;
		
		do {
			bufferCreated = true;
			try {
				this.canvas.createBufferStrategy(2);
			} catch(Exception e) {
				bufferCreated = false;
				try {
					Thread.sleep(200);
				} catch(InterruptedException ex) {
					
				}
			}
			
			if(num++ > 5) {
				break;
			}
		} while(!bufferCreated);
		
		if(!bufferCreated) {
			// Error
		} else {
			while(this.strategy == null) {
				try {
					this.strategy = this.canvas.getBufferStrategy();
				} catch(Exception e) {
					
				}
			}
			
			Graphics gfx = null;
			while(gfx == null) {
				try {
					gfx = this.getBackBuffer();
				} catch(Exception e) {
					
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Returns whether this <code>WindowedMode</code> is using a bufferstrategy
	 * or a volatile image.
	 * 
	 * @return	if a buffer strategy or a volatile image is used.
	 */
	public boolean isBufferStrategy() {
		return (this.strategy != null);
	}

	@Override
	public Graphics getBackBuffer() {
		if(this.currentGraphics.getGraphics() == null) {
			if(this.strategy == null) {
				if(this.offscreen.validate(WindowedMode.CONFIG) == 
					VolatileImage.IMAGE_INCOMPATIBLE) {
					this.createBackBuffer();
				}
				// assign graphics
				this.currentGraphics.setGraphics(this.offscreen.createGraphics());
			} else {
				this.currentGraphics.setGraphics((Graphics2D) this.strategy.getDrawGraphics());
			}
		}

		this.currentGraphics.setColor(Color.BLACK);
		this.currentGraphics.fillRect(0, 0, 640, 480);
		
		return this.currentGraphics;
	}

	@Override
	public boolean flip() {
		this.currentGraphics.getGraphics().dispose();
		this.currentGraphics.setGraphics(null);
		
		if(this.strategy == null) {
			this.canvas.getGraphics().drawImage(this.offscreen, 0, 0, null);
			Toolkit.getDefaultToolkit().sync();
			return (!this.offscreen.contentsLost());
		} else {
			this.strategy.show();
			Toolkit.getDefaultToolkit().sync();
			return (!this.strategy.contentsLost());
		}
	}

	@Override
	public void cleanup() {
		try {
			Thread.sleep(200L);
		} catch(InterruptedException e) {
			
		}
		
		try {
			if(this.frame != null) {
				this.frame.dispose();
			}
		} catch(Exception e) {
			
		}
	}

	@Override
	public Dimension getSize() {
		return this.size;
	}

	@Override
	public Image getWindowIcon() {
		return null;
	}

	@Override
	public String getWindowTitle() {
		return this.frame.getTitle();
	}

	@Override
	public void setWindowIcon(Image icon) {

	}

	@Override
	public void setWindowTitle(String title) {
		this.frame.setTitle(title);
	}
}